'use strict';
/**
 * Rtjs:app
 * Riot.js 3 generator
 */
/*
          \
         /  `.-~~--..--~~~-.
        <__                 `.
     ~~~~~~~~~~~~~~~~.~~~~,','
        TOMATO        .~~_,'
*/
const chalk = require('chalk');
const yosay = require('yosay');
// Additional deps
const path = require('path');
const glob = require('glob');
const username = require('git-user-name');
const spawn = require('child_process').spawn;
// Const extend = require('util')._extend;
// Base class extend from yeoman-generator
const join = path.join;
const dirname = join(__dirname, '..', '..');
const version = require(
    join(dirname, 'package.json')
).version;
const BaseClass = require(
    join(dirname, 'lib', 'base-class.js')
);
const config = require(
    join(dirname, 'lib', 'config.json')
);

const appPath = config.appPath;

// Const srcPath = config.srcPath; // use this when we have the rapture.js integration
// const destPath = config.destPath;
const developerName = 'to1source.com';
// Properties
const defaultLang = 'en';
// Class
module.exports = class extends BaseClass {

    // /////////////////////////////
    //       PROPERTIES          //
    // /////////////////////////////

    /**
     * available css frameworks
     * @return {array}
     */
  get cssFrameworks() {
    const version = 6;
    return [
            {name: 'Not using any css framework', value: 'none', npm: null},
            {name: `Bootstrap V.4 (alpha.${version})`, value: 'bootstrap4', npm: `bootstrap@4.0.0-alpha.${version}`},
            {name: `Daemonite's Material UI (alpha.${version})`, value: 'materialui', npm: `daemonite-material@4.0.0-alpha.${version}`},
            // {name: 'Office Fabric UI', value: 'fabricui' , npm: 'office-ui-fabric-core'}
            {name: 'Foundation 6', value: 'foundation6', npm: 'foundation-sites'},
            // {name: 'Semantic UI' , value: 'SemanticUI' , npm: ''}
            {name: 'Tachyons', value: 'tachyons', npm: 'tachyons'}
    ];
  }
    /**
     * Whether to use sass or not
     * @return {array}
     */
  get cssDevStyles() {
    return [
            {name: 'Plain CSS (with postcss cssnext plugin)', value: 'css'},
            {name: 'SASS preprocessor', value: 'scss'}
    ];
  }
    // Start
    /**
     * class constructor
     * @param {array} args
     * @param {object} opts
     */
  constructor(args, opts) {
    super(args, opts);
         // Determine the app name
    this.suggestAppName = (args.length) ? args[0] : path.basename(this.contextRoot);
         // Skip installation
    this.skipInstallation = opts['skip-installation-for-test-purpose-only']; // Should never use this!
         // Language options they need to provide the flag --lang=cn to switch
    this.lang = opts.lang ? opts.lang.toLowerCase() : defaultLang;
         // Try to use Monad but the problem is the value need to transform
         // and hard to switch to the raw value! Now I understand why it needs to be immutable!
    this._loadLangFile(this.lang);
         // Load the npm-list.json file
    this.npmList = require(join(__dirname, 'templates', 'npm-list.json'));
         // Store the lang and appName useful later
    this.config.set({
      appName: this.appName,
      lang: this.lang
    });
    // Console.log(this.config.set.toString()); // what the hell is inside?
    // @20170901 if we switch to npm then use the optional otherwise use bower
    this.optionals = this.npmList.bower.map(optional => {
        optional.checked = true;
        return optional;
    });
  }
    // Yeoman style privates
  __getNpmLibDeps() {
      // @20170901 falling back to use bower, still no good solution at the moment
      return [];
    /*
    let list = this.npmList.npm;
        // Add those that selected by the users
        // console.log(this.optionals);
    return list.concat(
        this.optionals.map( o => o.value )
    );
    */
  }
    /**
     * Get the list of bower dependencies
     * @return {array} list
     */
  __getBowerLibDeps() {
    let list = this.npmList.based; // @20170901 riot and riot-route are base deps
    if (this.props.cssFramework === 'none') {
      return list;
    }
    return list.concat(
             this.cssFrameworks.filter(e => {
               return e.value === this.props.cssFramework;
             }).map(e => {
               return e.npm;
             })
         );
  }
    /**
     * Getting the devDepenecies
     * @return {array} list
     */
  __getDevLibDeps() {
      // Append the test required deps here
    let list = this.npmList.dev.concat(this.npmList.jest);
    // Start adding other deps

    if (this.props.useGit) {
      list = list.concat(this.npmList.git);
    }
    if (this.props.cssDevStyle === 'scss') {
      return list.concat(this.npmList.scss);
    }
    return list.concat(this.npmList.postcss);
  }
    /**
     * Display a message during installation
     * @param {array} list
     * @param {string} title
     */
  __installMsg(list, title = 'INSTALLING') {
    this.log(chalk.white('INSTALLING ' + title));
    list.forEach(l => {
      this.log(chalk.white(' -> ' + l));
    });
  }
    /**
     * Core bower dependencies
     * @return {array}
     */
  __jsBowerLibDeps() {

    const list = this.__getBowerLibDeps().concat(
        this.props.optionalDeps
    );

    this.__installMsg(list, 'DEPENDECIES');
    return list;
  }
    /**
     * Dev dependencies
     * @return {array}
     */
  __jsDevLibDeps() {
    const list = this.__getDevLibDeps();
    this.__installMsg(list, 'DEV DEPENDECIES');
    return list;
  }
    /**
     * 2017-06-06 going back to use bower because using npm to manage browser deps is a joke
     * @param {function} callback
     */
  __cb(callback) {
    return (typeof callback === 'function') ? callback : function () {};
  }
    /**
     *
     * @param {function} cb
     */
  __installBowerDeps(cb) {
        // Need to combine the option and do the installation here
    return this.bowerInstall(
            this.__jsBowerLibDeps(),
            {save: true},
            this.__cb(cb)
        );
  }
  /**
   * Install the npm deps
   * @param {function} cb
   */
  __installNpmDeps(cb) {
    const packages = this.__getNpmLibDeps();

    const opt = {save: true};
    if (this.props.installer === 'cnpm') {
          // Const ex = spawn('cnpm' , ['install' , '--save']);
      return this.runInstall('cnpm', packages, opt, this.__cb(cb));
    }
      // Const opt = (this.props.installer === 'yarnInstall') ? {dev: true} : {save: true};
    return this[this.props.installer](
              packages,
              opt,
              this.__cb(cb)
          );
  }

    /**
     * Yarn or npm here (@TODO auto detect what they have got)
     * @param {function} cb
     */
  __installDevDeps(cb) {
    const packages = this.__jsDevLibDeps();
    if (this.props.installer === 'cnpm') {
        // Const ex = spawn('cnpm' , ['install' , '--save']);
      return this.runInstall('cnpm', packages, {saveDev: true}, this.__cb(cb));
    }

    const opt = (this.props.installer === 'yarnInstall') ? {dev: true} : {saveDev: true};
    return this[this.props.installer](
            packages,
            opt,
            this.__cb(cb)
        );
  }

    /**
     * Just copy the assets folder there is nothing but just a README file
     */
  __copyAssets() {
    this._copy(
            join('app', 'assets', 'README.md')
        );
  }
    /**
     * Copy every gulp related stuff here
     */
  __copyGulpFiles() {
    const isCss = this.props.cssDevStyle === 'css';
    const isSass = this.props.cssDevStyle === 'scss';
    this._copyTpl(
             'gulpfile.js',
            null, // 'gulpfile.js',
      {
        css: isCss,
        sass: isSass,
        useGit: this.props.useGit
      }
         );
    const templatePath = join(__dirname, 'templates');
    glob(join(templatePath, 'gulp-scripts', '**', '*.*'), (er, files) => {
      if (er) {
        this.log(chalk.red('globbing gulp files error!'));
        throw er;
      }
      files.forEach(src => {
                // Skip the file based on how they develop their style
        const filename = path.basename(src);
        if (isCss && filename === 'sass.js') {
          return;
        }
        if (isSass && filename === 'css.js') {
          return;
        }
                // Not a default template path
        this._copy(
                    src.replace(templatePath, ''),
                    join('gulp-scripts', path.basename(src))
                );
      });
    });
  }
    /**
     * This one needs a lot of calculations ...
     */
  __copyAppFiles() {
    const basePath = join(appPath, 'scripts', 'components');
    const framework = this.props.cssFramework;
    const frameworkBase = join(basePath, framework);
         // Babel profile - should be in the root folder
         // @UPDATE we might need to have separate babel file for test and app code
    this._copy(join(appPath, 'babelrc'), join('.babelrc'));
         // Main.js
    this._copyTpl(
            [appPath, 'scripts', 'main.js'],
            null, // [appPath, 'scripts', 'main.js'],
            {cssFramework: framework}
         );
    // @2016-08-30 add vendor.js
    /*
    @20170901 it's still fucked don't use it
    this._copyTpl(
        [appPath , 'scripts' , 'vendor.js'] ,
        null ,
        {
            optionals: this.props.optionalDeps.map( o =>
            {
                return this.npmList.optionals.filter( x =>
                {
                    return x.value === o;
                })[0]
            }
        });
    */
         // Tag files
    this._copy(
             join(basePath, 'app', 'app.tag')
         );
    this._copy(
             join(frameworkBase, 'home', 'home.tag'),
             join(basePath, 'home', 'home.tag')
         );
    this._copy(
             join(frameworkBase, 'about', 'about.tag'),
             join(basePath, 'about', 'about.tag')
         );
         // The rest of the apps
    this._copy(
             join(frameworkBase, 'app', 'app-route.tag'),
             join(basePath, 'app', 'app-route.tag')
         );
    this._copyTpl(
             [frameworkBase, 'app', 'app-nav.tag'],
             [basePath, 'app', 'app-nav.tag'],
             {appName: this.props.appName}
         );
  }
  /**
   * Copy the test files
   */
  __copyTestFiles() {
    const testPath = config.testPath;
    this._copy(
          join(testPath, 'tags', 'about.test.js')
      );
    this._copy(
          join(testPath, 'tags', 'home.test.js')
      );
  }
    /**
     * Default type to css - @TODO remove this reference we are going back to use bower
     * @param {string} type
     */
  __getCssFilePath(type = 'css') {
    const paths = {
      bootstrap4: {
        css: join('bootstrap', 'dist', 'css', 'bootstrap.css'),
        scss: join('bootstrap', 'scss', 'bootstrap.scss')
      },
      materialui: {
        css: join('daemonite-material', 'css', 'material.css'),
        scss: join('daemonite-material', 'assets', 'sass', 'material.scss')
      },
      foundation6: {
        css: join('foundation-sites', 'dist', 'css', 'foundation.css'),
        scss: join('foundation-sites', 'assets', 'foundation.scss')
      },
      tachyons: {
        css: join('tachyons-css', 'css', 'tachyons.css'),
        scss: join('tachyons-css', 'css', 'tachyons.css')
      },
      none: {
        css: null,
        scss: null
      }
    };
    return paths[this.props.cssFramework][type];
  }
    /**
     * For the index file
     */
  __copyHtmlFile() {
    let opt = {
      title: this.props.appName,
      lang: this.lang,
      cssFilePath: false,
      cssFramework: this.props.cssFramework
    };
    if (this.props.cssFramework !== 'none' && this.props.cssDevStyle === 'css') {
      opt.cssFilePath = this.__getCssFilePath();
    }
    this._copyTpl(
             [appPath, 'index.html'],
             null, // [appPath, 'index.html'],
             opt
         );
  }
    /**
     * What the name said
     */
  __copyStyleFile() {
    if (this.props.cssDevStyle === 'scss') {
      let basePath = this.__getCssFilePath('scss');
      this._copyTpl(
                 [appPath, 'styles', 'main.scss'],
                 null, // [appPath, 'styles', 'main.scss'],
        {
          cssFrameworkFilePath: basePath,
          appName: this.props.appName
        }
             );
    } else {
      this._copyTpl(
                [appPath, 'styles', 'main.css'],
                null,
                {appName: this.props.appName}
            );
    }
  }
    /**
     * What the name said
     */
  __copyBaseFiles() {
    this._copy('gitignore', '.gitignore');
    ['eslintrc.js'].forEach(file => {
      this._copy(file);
    });
         // 'nb.config.js'
    this._copyTpl(
             'nb.config.js',
             null,
             config
         );
  }
    /**
     * Run the gulp command at the end
     * @2017-07-09 add the git sub command and switch if useGit
     */
  __gulpDev() {
    const cmd = this.props.useGit ? 'project:init' : 'dev';
    const ls = spawn('gulp', [cmd]);

    this.log(
         chalk.white(
             this.langObj.runningGulpForYou.replace('{{cmd}}', cmd)
         )
    );

    ls.stdout.on('data', data => {
      this.log(`${data}`);
    });
    ls.stderr.on('data', data => {
      this.log(chalk.yellow(`stderr: ${data}`));
    });
    ls.on('close', code => {
      this.log(chalk.red(`child process exited with code ${code}`));
    });
  }
     /**
      * Have Yeoman greet the user.
      */
  __begin() {
    this.log(
             yosay(
                 this.langObj.greeting.replace(
                     '{{generatorName}}',
                     chalk.red('generator-rtjs')
                 ).replace(
                     '{{version}}',
                     version
                 )
             )
         );
  }
    /**
     * Final message stuff
     */
  __final() {
        // "gulpjs/gulp.git#4.0", --> gulp 4 keep on failing --> 2017-06-03 turns out it was npm@5.0.1 bug
    this.config.save(); // Save the preference
    this.log(
            this.langObj.bye
        );
        // Should execute the `gulp dev` now
    this.__gulpDev();
  }
    /**
     * Return the list of prompts
     * @param {boolean} yanInstalled from _checkIfYarnInstalled
     */
  __getPrompts(yarnInstalled) {
    let installChoices = [
                {name: 'npm', value: 'npmInstall'},
                {name: 'yarn', value: 'yarnInstall'}
    ];
    const cn = this.lang === 'cn';

    if (cn) {
      installChoices.push({name: 'cnpm', vlaue: 'cnpm'});
    }
    return [{
      type: 'input',
      name: 'appName',
      message: this.langObj.appName,
      default: this.suggestAppName
    },
    {
      type: 'list',
      name: 'cssFramework',
      choices: this.cssFrameworks,
      message: this.langObj.cssFramework
    }, {
      when: props => {
        return props.cssFramework !== 'none';
      },
      type: 'list',
      name: 'cssDevStyle',
      message: this.langObj.cssDevStyle,
      choices: this.cssDevStyles
    }, {
      type: 'checkbox',
      name: 'optionalDeps',
      choices: this.optionals,
      message: this.langObj.optionals
    }, {
      type: 'confirm',
      name: 'useGit',
      message: this.langObj.useGit,
      default: true
    }, {
      when: props => {
        if (cn) { // Then we need to let them choose
          return cn;
        }
        if (yarnInstalled) {
          props.installer = 'yarnInstall';
        }
        return !yarnInstalled;
      },
      type: 'list',
      name: 'installer',
      message: this.langObj.installer,
      choices: installChoices,
      default: cn ? 'cnpm' : 'npmInstall'
    }];
  }

    // HOOKS
    /**
     * the actual q & a
     */
  prompting() {
    this.__begin();
    return this._checkIfYarnInstalled().then(yarnInstalled => {
      return this.prompt(
                this.__getPrompts(yarnInstalled)
            ).then(props => {
              this.props = props;
            });
    });
  }
    /**
     * Creating files
     */
  writing() {
    ['package.json', 'bower.json'].forEach(file => {
      this.fs.copyTpl(
                this.templatePath(file),
                this.destinationPath(file),
        {
          appName: this.props.appName,
          author: username() || developerName,
          cssFramework: this.props.cssFramework
        }
            );
    });
        // Copy tasks
    this.__copyGulpFiles();
    this.__copyBaseFiles();
    this.__copyAppFiles();
    this.__copyTestFiles();
    this.__copyHtmlFile();
    this.__copyStyleFile();
    this.__copyAssets();
  }
    /**
     * Running the dependencies installation
     */
  install() {
    if (!this.skipInstallation) { // For testing purpose only!
      this.__installNpmDeps(() => {
        this.__installBowerDeps(() => {
          this.__installDevDeps(
                          () => this.__final()
                      );
        });
      });
    }
  }
};

// --- EOF ---
